package com.visec.hdy.transmission.socket.code;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.Closeable;
import java.io.IOException;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import com.visec.hdy.transmission.socket.config.SocketModel;

public class SocketClientImp implements SocketClient {

	private volatile Socket socket;
	private volatile SocketModel socketModel;
	private volatile BufferedInputStream bufferedInputStream;
	private volatile BufferedOutputStream bufferedOutputStream;
	private Map<String, MonitorThread> monitorMap;

	class MonitorThread extends Thread implements Closeable {

		private Monitor m;
		private SocketClient socketClient;

		public MonitorThread(Monitor m, SocketClient socketClient) {
			this.m = m;
			this.socketClient = socketClient;
		}

		@Override
		public void run() {
			// TODO Auto-generated method stub
			socketClient.openInput();
			byte[] buffer = new byte[socketModel.getBufferSize()];
			int a = 0;
			while ((a = socketClient.read(buffer)) > 0) {
				if (m.handle(buffer, 0, a))
					break;
			}
			try {
				close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		@Override
		public void close() throws IOException {
			// TODO Auto-generated method stub
			this.socketClient.close();
		}

	}

	protected SocketClientImp() {
		// TODO Auto-generated constructor stub
	}

	@Override
	public void close() throws IOException {
		// TODO Auto-generated method stub
		if (bufferedInputStream != null) {
			try {
				bufferedInputStream.close();
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		if (bufferedOutputStream != null) {
			try {
				bufferedOutputStream.close();
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		if (socket != null) {
			try {
				socket.close();
			} catch (Exception e) {
				// TODO: handle exception
			}
		}
		bufferedInputStream = null;
		bufferedOutputStream = null;
		socket = null;
	}

	public boolean connect() throws Exception {
		if (socket != null && socket.isConnected())
			return true;
		socket = new Socket(socketModel.getIp(), socketModel.getPort());
		socket.setSoTimeout(0);
		return true;
	}

	public static SocketClient getInstance(Socket s) {
		return getInstance(s, "utf-8");
	}

	public static SocketClient getInstance(Socket s, String charset) {
		SocketClientImp sci = new SocketClientImp();
		sci.socket = s;
		sci.monitorMap = new HashMap<String, MonitorThread>();
		sci.socketModel = new SocketModel();
		sci.socketModel.setCharset(charset);
		return sci;
	}

	public static SocketClient getInstance(SocketModel sm) {
		SocketClientImp sc = new SocketClientImp();
		sc.socketModel = sm;
		sc.monitorMap = new HashMap<String, MonitorThread>();
		return sc;
	}

	@Override
	public void openOutput() {
		// TODO Auto-generated method stub
		try {
			if (bufferedOutputStream != null)
				return;
			connect();
			bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void closeOutput() {
		// TODO Auto-generated method stub
		try {
			if (bufferedOutputStream == null)
				throw new IOException("output stream no open!");
			bufferedOutputStream.close();
			bufferedOutputStream = null;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void write(byte[] b) throws Exception {
		// TODO Auto-generated method stub
		write(b, 0, b.length);
	}

	@Override
	public void write(byte[] b, int start, int length) throws Exception {
		// TODO Auto-generated method stub
		openOutput();
		bufferedOutputStream.write(b, start, length);
		bufferedOutputStream.flush();
	}

	@Override
	public void openInput() {
		// TODO Auto-generated method stub
		try {
			if (bufferedInputStream != null)
				return;
			connect();
			bufferedInputStream = new BufferedInputStream(socket.getInputStream());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void closeInput() {
		// TODO Auto-generated method stub
		try {
			if (bufferedInputStream == null)
				throw new IOException("input stream no open!");
			bufferedInputStream.close();
			bufferedInputStream = null;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public int read(byte[] b) {
		// TODO Auto-generated method stub
		openInput();
		try {
			return bufferedInputStream.read(b);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			return -2;
		}
	}

	@Override
	public synchronized void monitor(String monitorId, Monitor m) {
		// TODO Auto-generated method stub
		clearMonitor(monitorId);
		SocketClient socketClient = null;
		Socket s = null;
		try {
			if (socketModel == null)
				throw new IOException("this is not a client socket");
			s = new Socket(socketModel.getIp(), socketModel.getPort());
			socketClient = SocketClientImp.getInstance(s);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		MonitorThread mt = new MonitorThread(m, socketClient);
		mt.start();
		monitorMap.put(monitorId, mt);
	}

	@Override
	public synchronized void clearMonitor(String monitorId) {
		// TODO Auto-generated method stub
		if (monitorMap.containsKey(monitorId))
			try {
				monitorMap.get(monitorId).close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}

	@Override
	public synchronized void clearMonitors() {
		// TODO Auto-generated method stub
		Set<String> keySet = monitorMap.keySet();
		for (String key : keySet)
			clearMonitor(key);
	}

	@Override
	public void write(String s) throws Exception {
		// TODO Auto-generated method stub
		write(s.getBytes(socketModel.getCharset()));
	}

	@Override
	public SocketClient copy() {
		// TODO Auto-generated method stub
		return SocketClientImp.getInstance(socketModel);
	}

	@Override
	public Charset getCharset() {
		// TODO Auto-generated method stub
		return Charset.forName(socketModel.getCharset());
	}

	@Override
	public void writeCompletion(String s) throws Exception {
		// TODO Auto-generated method stub
		byte[] bytes = s.getBytes(socketModel.getCharset());
		int index = 0;
		byte[] b = new byte[socketModel.getBufferSize()];
		while ((bytes.length - index) > socketModel.getBufferSize()) {
			System.arraycopy(bytes, index, b, 0, socketModel.getBufferSize());
			index += socketModel.getBufferSize();
			write(b);
		}
		System.arraycopy(bytes, index, b, 0, (bytes.length - index));
		for (int i = (bytes.length - index); i < socketModel.getBufferSize(); i++) {
			b[i] = 1;
		}
		write(b);
	}

	@Override
	public BufferedInputStream getInput() {
		// TODO Auto-generated method stub
		return bufferedInputStream;
	}

	@Override
	public boolean isConnected() {
		// TODO Auto-generated method stub
		if (socket == null)
			return false;
		return socket.isConnected();
	}

	@Override
	public boolean isClosed() {
		// TODO Auto-generated method stub
		if (socket == null)
			return false;
		return socket.isClosed();
	}

}
